package org.custom.spring.boot.mybatis.utils;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.ibatis.binding.BindingException;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.type.JdbcType;
import org.custom.spring.boot.constant.MagicValue;
import org.custom.spring.boot.mybatis.constant.MybatisConstant;
import org.custom.spring.boot.mybatis.entity.MybatisMapping;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;

import static cn.hutool.core.util.StrUtil.EMPTY;

/**
 * Mybatis-PlugIn 相关工具类
 *
 * @author 李坤
 */
public final class MybatisPlugInUtil {
    /**
     * 格式化 SQL，使其更加符合阅读习惯
     *
     * @param sql 原始SQL
     *
     * @return 格式化后的SQL
     */
    public static String formatSql(String sql) {
        // 格式化去除 SQL 中的换行符
        sql = sql.replace(MagicValue.WRAP, MagicValue.SPACE);

        // 格式化去除 SQL 中可能存在的 双空格 符
        while (sql.contains(MagicValue.SPACE2)) {
            sql = sql.replace(MagicValue.SPACE2, MagicValue.SPACE);
        }
        return sql.trim();
    }

    /**
     * 解析获取参数映射
     *
     * @param param 参数
     *
     * @return 参数Map
     */
    public static MapperMethod.ParamMap parse2ParamMap(Object param) {
        MapperMethod.ParamMap paramMap = null;

        if (param instanceof MapperMethod.ParamMap) {
            paramMap = (MapperMethod.ParamMap) param;
        } else {
            paramMap = new MapperMethod.ParamMap();

            if (param instanceof Map) {
                paramMap.putAll(((Map) param));
            } else if (param instanceof Object) {
                paramMap.put(MybatisConstant.PARAMS[0], param);
            }
        }
        return paramMap;
    }

    /**
     * 构建 Mybatis 参数映射数据
     *
     * @param boundSql SQL元数据
     *
     * @return 参数映射
     */
    public static List<MybatisMapping> parseParameterMapping(BoundSql boundSql) {
        MapperMethod.ParamMap paramMap = MybatisPlugInUtil.parse2ParamMap(boundSql.getParameterObject());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        List<MybatisMapping> result = new ArrayList<>(parameterMappings.size());

        if (ObjectUtil.isNotEmpty(parameterMappings)) {
            // 遍历获取 MybatisMapping 对象
            for (int index = 0; index < parameterMappings.size(); index++) {
                // 获取 Mapping 映射
                ParameterMapping mapping = parameterMappings.get(index);
                // 获取参数的 JDBC_TYPE类 型
                JdbcType jdbcType = Optional.ofNullable(mapping.getJdbcType()).orElse(JdbcType.VARCHAR);
                // 构造 MybatisMapping 对象并将其放入到集合中
                result.add(MybatisPlugInUtil.getParamValue(mapping.getProperty(), paramMap, boundSql, index).jdbcType(jdbcType).build());
            }
        }
        return result;
    }

    public static String[] splitSuitableKeywordByMetaSql(String metaSql, String... keywords) {
        String[] splitMetaSql = null;
        // 遍历处理可能有效的关键字
        for (String keyword : keywords) {
            // 判断MetaSql中是否包含该关键字
            if (StrUtil.isNotEmpty(keyword = judgeMetaSqlContainKeyword(metaSql, keyword))) {
                // 判断该关键字是否有效
                if ((splitMetaSql = metaSql.split(keyword)).length != 2) {
                    splitMetaSql = new String[]{keyword, splitMetaSql[0], splitMetaSql[1]};
                } else {
                    int apostropheCount = 0;
                    AtomicBoolean hasFindKeyword = new AtomicBoolean(false);
                    StringBuilder prefix = new StringBuilder(), suffix = new StringBuilder();

                    for (int index = 0; index < splitMetaSql.length; index++) {
                        if (hasFindKeyword.get()) {
                            suffix.append(splitMetaSql[index]);
                            if (index != splitMetaSql.length - 1) {
                                suffix.append(keyword);
                            }
                        } else {
                            prefix.append(splitMetaSql[index]).append(keyword);
                            hasFindKeyword.set(calculateSizeOfApostrophe(splitMetaSql[index], apostropheCount) % 2 == 0);
                        }
                    }
                    int index = prefix.lastIndexOf(keyword);
                    splitMetaSql = new String[]{keyword, prefix.substring(0, index), suffix.toString()};
                }
                break;
            }
        }

        return splitMetaSql;
    }

    /*--------------------------------------------------private methods-----------------------------------------------*/

    /**
     * 获取指定Key的参数值
     *
     * @param property 参数
     * @param paramMap 参数值映射
     * @param boundSql 元SQL数据
     * @param index    本次索引
     *
     * @return 参数Value
     */
    private static MybatisMapping.MybatisMappingBuilder getParamValue(String property, MapperMethod.ParamMap paramMap, BoundSql boundSql, int index) {
        // 复合参数的上层
        Object parentValue = null;
        MybatisMapping.MybatisMappingBuilder builder = MybatisMapping.builder();

        // 判断参数是否为复合对象
        if (!property.contains(MagicValue.POINT)) {
            // 处理参数为非复合对象 || 未取别名的复合对象 的情况
            builder.key(property).value(MybatisPlugInUtil.getParamParentValue(property, index, paramMap));
        } else {
            // 根据 分隔符-. 将其划分为数组
            String[] pointSplit = property.split(MagicValue.SPLIT_POINT);
            // 判断参数是否为 集合 或 复合对象
            if (property.startsWith(MybatisConstant.FOREACH_PREFIX)) {
                if (boundSql.hasAdditionalParameter(pointSplit[0])) {
                    parentValue = boundSql.getAdditionalParameter(pointSplit[0]);
                }
            } else {
                parentValue = MybatisPlugInUtil.getParamParentValue(pointSplit[0], index, paramMap);
            }
            builder.key(pointSplit[pointSplit.length - 1]);
            // 递归寻找最终属性的值，并将值构建到 MybatisMapping 中
            builder.value(MybatisPlugInUtil.recursionSearchValue(parentValue, 1, pointSplit));
        }

        return builder;
    }

    /**
     * 计算给定的SQL片段中 单引号-' 的数量 + 给定的数量
     *
     * @param metaSql 给定的SQL片段
     * @param count   现存数量
     *
     * @return 最终数量
     */
    private static int calculateSizeOfApostrophe(String metaSql, int count) {
        for (char data : metaSql.toCharArray()) {
            if (data == MagicValue.CHAR_APOSTROPHE) {
                count++;
            }
        }
        return count;
    }

    /**
     * 递归获取最终的值
     *
     * @param source 当前源对象
     * @param fields 字段集合
     * @param index  当前下标
     *
     * @return 最终值
     */
    private static Object recursionSearchValue(Object source, int index, String... fields) {
        if (source instanceof Map) {
            source = ((Map) source).get(fields[index++]);
        } else {
            source = ReflectUtil.getFieldValue(source, fields[index++]);
        }

        return fields.length == index ? source : recursionSearchValue(source, index, fields);
    }

    /**
     * 获取给定 参数-Key值 的映射Value
     *
     * @return 参数值
     */
    private static Object getParamParentValue(String key, int index, MapperMethod.ParamMap paramMap) {
        Object result = null;
        try {
            result = paramMap.get(key);
        } catch (BindingException bindingException) {
            Object source = null;
            try {
                source = paramMap.get(MybatisPlugInUtil.getParam(index));
            } catch (Exception exception) {
                source = paramMap.get(MybatisPlugInUtil.getParam(0));
            }
            result = ReflectUtil.getFieldValue(source, key);
        }
        return result;
    }

    /**
     * 获取默认参数映射Key
     *
     * @param index 下标
     *
     * @return 默认Key
     */
    public static String getParam(int index) {
        return (index < MybatisConstant.PARAMS.length) ? MybatisConstant.PARAMS[index] : MybatisConstant.PARAM + (index + 1);
    }

    /**
     * 判断给定的MetaSql中是否存在该关键字
     *
     * @param metaSql 元SQL数据
     * @param keyword 关键字
     *
     * @return 判断结果(不存在则返回空字符串)
     */
    private static String judgeMetaSqlContainKeyword(String metaSql, String keyword) {
        return metaSql.contains(keyword) ? keyword : (metaSql.contains(keyword = keyword.toUpperCase()) ? keyword : EMPTY);
    }
}
